package emergency;

import java.util.ArrayList;

import unit.Garage;
import unit.Unit;

import movement.Location;
import dispatch.DispatchCenter;

public class Disaster extends Emergency {

	/**
	 * Method for computing the average Location of the given Emergencies
	 * 
	 * @param emergencies
	 *            , the emergencies where we want to know the average Location
	 *            of
	 * 
	 * @return Returns the average location of the given Emergencies
	 * 
	 * @throws IllegalArgumentException
	 *             , if this disaster hasn't got any emergencies
	 */
	private static Location computeLocation(ArrayList<Emergency> emergencies) {
		int totalX = 0, totalY = 0, aantal = 0;

		for (Emergency e : emergencies) {
			totalX += e.getLocation().getX();
			totalY += e.getLocation().getY();
			aantal++;
		}

		if (aantal == 0)
			throw new IllegalArgumentException(
					"A disaster must have emergencies");

		return new Location(totalX / aantal, totalY / aantal,
				"DisasterLocation");
	}

	/**
	 * Method for computing the highest severity
	 * 
	 * @param emergencies
	 *            , the emergencies where we want to know the highest Severity
	 *            of
	 * 
	 * @return Returns the highest severity of the given Emergencies
	 */
	private static Severity computeSeverity(ArrayList<Emergency> emergencies) {
		Severity worstSev = Severity.BENIGN;

		for (Emergency e : emergencies)
			if (e.getSeverity().getNiveau() > worstSev.getNiveau())
				worstSev = e.getSeverity();

		return worstSev;
	}
	
	/**
	 * Variable registering the emergencies of this Disaster
	 */
	private ArrayList<Emergency> emergencies;

	private ArrayList<Unit> unitsToRevoke;

	/**
	 * Variable registering the description of this Disaster (ex: Earthquake)
	 */
	private String description;

	/**
	 * Constructor for creating a Disaster
	 * 
	 * @param dispatchCenter
	 *            , the dispatchcenter where this Disaster has to be assigned on
	 * 
	 * @param emergencies
	 *            , the emergencies concerning this Disaster
	 * 
	 * @param description
	 *            , the description of this Disaster (ex: Earthquake)
	 * 
	 * @post The location of this Disaster is set to the average location of all
	 *       the emergencies
	 * 
	 * @post The Severity of this Disaster is set to the highest Severity of
	 *       it's emergencies
	 * 
	 * @post This Disaster is added to the given DispatchCenter
	 * 
	 * @post The description of this Disaster is set to the given description
	 * 
	 * @post The emergencies of this Disaster are set to the given emergencies
	 * 
	 * @post The disaster of the given emergencies are set
	 * 
	 * @throws IllegalArgumentException
	 *             , if the the emergencies are empty
	 * 
	 */
	public Disaster(DispatchCenter dispatchCenter,
			ArrayList<Emergency> emergencies, String description) {
		super(computeLocation(emergencies), computeSeverity(emergencies),
				dispatchCenter, new EmergencyPolicyDefault());
		unitsToRevoke = new ArrayList<Unit>();
		if (emergencies.size() != 0) {
			setEmergencies(emergencies);
			setDescription(description);

			for (Emergency e : emergencies)
				e.setDisaster(this);

		} else {
			throw new IllegalArgumentException(
					"A disaster must have emergencies");
		}

	}

	/**
	 * Method for checking all the emergencies of this disaster are completed
	 * 
	 * @return Returns true if alle emergencies are completed, false otherwise
	 */
	public boolean allEmergenciesCompleted() {
		for (Emergency e : getEmergenciesClone()) {
			if (!e.getStateName().equalsIgnoreCase("statecompleted"))
				return false;
		}

		return true;
	}

	/**
	 * Assign all the available units of this garage needed for a given
	 * emergency to this emergency
	 * 
	 * @param garage
	 *            The garage you want to use to search units , needed for this
	 *            emergency
	 * 
	 * @post The needed units are assigned to this emergency
	 * 
	 * @post The needed units are send to this emergency it's location, after
	 *       assigning the last unit
	 * 
	 */
	public void assignAllUnits(Garage garage) {
		ArrayList<Unit> unitsToAssign = getSuggestion(garage);

		for (Unit u : unitsToAssign) {
			u.assignEmergency(this);
			getEmergencyRequirements().setRequirements(this);
		}

		if (getTotalUnitsNeeded() == getUnits().size()) {
			sendUnits();
		}
	}

	/**
	 * Method for assign 1 unit to this Disaster
	 * 
	 * @post The given unit is assigned to this disaster
	 * 
	 * @post If this was the last unit needed on the emergency, all the units
	 *       are sent to the correct emergency
	 */
	public void assignUnit(Unit u) {
		super.assignUnit(u);
		dispatchUnits();

	}

	/**
	 * Method to complete this disaster, depending in which state this emergency
	 * occurs
	 * 
	 * @post This emergency is complete, or an error handles the problem
	 * 
	 */
	public void complete() {
		
		int completed = 0;
		for (Emergency e  : getEmergenciesClone()){
			if((e.getState() instanceof StateCanceled) || (e.getState() instanceof StateCompleted))
				completed++;
		}
		if (completed == getEmergenciesClone().size()){
			getState().complete(this);
		}
	}

	/**
	 * Method for dispatching the units to the right emergency
	 * 
	 * @post Units are first being dispatched to the serious/urgent emergencies
	 *       , later to the less urgent emergencies
	 * 
	 */
	public void dispatchUnits() {
		if (getState() instanceof StateInProgress) {
			for (Emergency e : getEmergencies()) {
				if (e.getState() instanceof StateRecorded) {
					if (e.getSeverity().equals(Severity.SERIOUS)
							|| e.getSeverity().equals(Severity.URGENT)) {
						ArrayList<Unit> unitsToAssign = e.createSuggestion(getAvailableUnitsClone());
						for (Unit u : unitsToAssign) {
							u.assignEmergency(e);
							getUnits().remove(u);
						}
					}
				}
			}
			try {
				for (Emergency e : getEmergencies()) {
					if (e.getState() instanceof StateRecorded) {
						ArrayList<Unit> unitsToAssign = e
								.createSuggestion(getAvailableUnitsClone());
						for (Unit u : unitsToAssign) {
							u.assignEmergency(e);
							getUnits().remove(u);
						}
					}
				}
			} catch (IllegalArgumentException e) {
				// wait till more units are available
			}
		}
	}

	public ArrayList<Unit> getAvailableUnitsClone() {
		ArrayList<Unit> available = new ArrayList<Unit>();
		for(Unit u : getUnitsClone()){
			if (!u.isBroken())
			available.add(u);
		}
		return available;
	}

	/**
	 * Getter for getting the Description of this Disaster
	 * 
	 * @return Returns the description of this Disaster
	 */
	@Override
	public String getDescription() {
		return description;
	}

	/**
	 * Method for getting the emergency-list
	 * 
	 * @return Returns the list of the emergencies of this Disaster
	 */
	private ArrayList<Emergency> getEmergencies() {
		return emergencies;
	}

	/**
	 * Method for getting a Clone of the emergency-list
	 * 
	 * @return Returns a cloned list of the emergencies of this Disaster
	 */
	@SuppressWarnings("unchecked")
	@Override
	public ArrayList<Emergency> getEmergenciesClone() {
		return (ArrayList<Emergency>) emergencies.clone();
	}

	public ArrayList<Unit> getUnitsToRevoke(){
		return unitsToRevoke;
	}

	public void markToRevoke(Unit unit) {
		unitsToRevoke.add(unit);
	}

	/**
	 * Method for moving the units, assigning them to the correct emergency
	 * within the disaster
	 * 
	 * @post Units are being dispatched
	 */
	protected void moveUnits() {
		dispatchUnits();
	}

	/**
	 * Method for revoking the units of this disaster
	 * 
	 * @post The units of this disaster are revoked/removed from this disaster
	 */
	@Override
	public void revokeUnits() {
		ArrayList<Unit> unitsToRemove = new ArrayList<Unit>();

		for (Unit u : getUnits()) {
			unitsToRemove.add(u);
			u.releaseFromDisaster();
		}

		for (Unit u : unitsToRemove)
			getUnits().remove(u);

	}

	/**
	 * Setters for setting the description of this Disaster
	 * 
	 * @param description
	 *            , the Description of this Disaster (ex: Forest Fire)
	 * 
	 * @post The description of this Disaster is set to the given description
	 */
	private void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Setters for setting the emergencies of this Disaster
	 * 
	 * @param emergencies
	 *            , the emergencies of this Disaster
	 * 
	 * @post The emergencies of this Disaster are set to the given emergencies
	 * 
	 * @throws IllegalArgumentException
	 *             , if there is at least one invalid emergency
	 */
	private void setEmergencies(ArrayList<Emergency> emergencies) {
		if (validEmergencies(emergencies))
			this.emergencies = emergencies;
		else
			throw new IllegalArgumentException(
					"Not all the emergencies of disaster: " + getDescription()
							+ " are valid!");
	}

	/**
	 * Method for getting a string representation of this Disaster
	 * 
	 * @return Returns the string representation of this Disaster
	 */
	@Override
	public String toString() {
		String output = "severity is " + getSeverity().toString()
				+ "  =  DISASTER: " + getDescription();
		for (Emergency e : getEmergencies())
			output += "\n\t   -" + e.toString();
		return output;
	}

	/**
	 * Method for overriding the unitBrokeDown of Emergency, since units that
	 * break down in a disaster, still need to be part of the disaster
	 * 
	 * @post The given (broken)unit is NOT released from this disaster
	 */

	@Override
	public void unitBrokeDown(Unit unit) {
	}

	/**
	 * Method to do the actions to complete the given unit it's task in this
	 * disaster
	 * 
	 * @post The given unit is revoked, stays in this disaster or is assigned to
	 *       another emergency within this disaster
	 * 
	 * @post If the given unit is the last unit who completed it's job from the
	 *       disaster, it's completed
	 */
	@Override
	public void unitCompletedEmergency(Unit unit) {
		unit.forceRevokeUnit();

		if (getUnitsClone().size() == 0)
			complete();
	}

	/**
	 * Method for checking the given emergencies are valid (no doubles, no
	 * disaster in disaster)
	 * 
	 * @param emergencies
	 *            , The emergencies that need to be checked for validity
	 * 
	 * @return Return true, if valid, false if one emergency occurs more than
	 *         once, or is a Disaster
	 */
	@SuppressWarnings("unchecked")
	private boolean validEmergencies(ArrayList<Emergency> emergencies) {
		boolean valid = true;

		for (Emergency e : emergencies) { // No disaster in disaster allowed
			if (e instanceof Disaster)
				valid = false;

			ArrayList<Emergency> newList = (ArrayList<Emergency>) emergencies
					.clone();
			newList.remove(e);

			if (newList.contains(e))
				valid = false;
		}
		return valid;
	}



}
